package cloudspace.vm.javassist;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import cloudspace.vm.javassist.AbstractCommand.SignatureType;

import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.expr.Cast;
import javassist.expr.ConstructorCall;
import javassist.expr.Expr;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.Handler;
import javassist.expr.Instanceof;
import javassist.expr.MethodCall;
import javassist.expr.NewArray;
import javassist.expr.NewExpr;

/**
 * The Class CustomExprEditor. This class is responsible for actually editing
 * the
 */
public class CustomExprEditor extends ExprEditor
{
	Map<SignatureType, Map<String, List<CommandInterface>>> ExpressionNameToCommand;


	/**
	 * Instantiates a new custom expr editor. from a list of commands. These
	 * commands are stored in seperate lists to be executed on method calls and
	 * expressions being translated.
	 * 
	 * @param editConfigs
	 *            commands to drive the configuration.
	 */
	public CustomExprEditor(List<CommandInterface> editConfigs)
	{
		ExpressionNameToCommand = new HashMap<SignatureType, Map<String, List<CommandInterface>>>();
		for (CommandInterface curCommand : editConfigs)
		{

			// CommandInterface curCommand = commandIter.next();
			Map<String, List<CommandInterface>> sigMap = ExpressionNameToCommand
					.get(curCommand.getSignatureType());
			if (sigMap == null)
			{
				sigMap = new HashMap<String, List<CommandInterface>>();
				ExpressionNameToCommand.put(curCommand.getSignatureType(),
						sigMap);
			}
			List<CommandInterface> commands = sigMap.get(curCommand
					.getSignature());
			if (commands == null)
			{
				commands = new LinkedList<CommandInterface>();
				sigMap.put(curCommand.getSignature(), commands);
			}
			commands.add(curCommand);
		}
	}

	/**
	 * this method is for static edits to a class. These happen once and are not
	 * iterated.
	 * 
	 * @param clazz
	 *            the clazz to be edited
	 */
	public synchronized void staticEdits(CtClass clazz)
	{
		Map<String, List<CommandInterface>> sigTypeMap = ExpressionNameToCommand
				.get(SignatureType.METHODBODY);
		performStaticEdits(clazz, sigTypeMap);
		sigTypeMap = ExpressionNameToCommand
				.get(SignatureType.STATICDEFINITION);
		performStaticEdits(clazz, sigTypeMap);
	}

	private void performStaticEdits(CtClass clazz,
			Map<String, List<CommandInterface>> sigTypeMap)
	{
		if (sigTypeMap != null)
		{
			for (CtMethod method : clazz.getDeclaredMethods())
			{
				String methodName = method.getLongName();
				int nameStart = method.getLongName().substring(0,method.getLongName().indexOf('(')).lastIndexOf('.');
				methodName = methodName.substring(nameStart+1);
				List<CommandInterface> listOfCommands = sigTypeMap.get(methodName);
				if (listOfCommands != null)
				{
					for (CommandInterface curCommand : listOfCommands)
					{
						// CommandInterface curCommand = iterCommand.next();
						try
						{
							curCommand.translate(method,methodName);
						}
						catch (Exception e)
						{
							e.printStackTrace();
							System.err
									.println("Command is being removed because it is incorrectly configured");
							// staticDefCommands.remove(curCommand);
						}
					}
				}
			}
		}
	}

	/**
	 * Conversion of an Expr.
	 * 
	 * @param ccall
	 *            the expression ccall
	 * @param commandList
	 *            the list of commands to use to perform the edit.
	 * @param expressionName
	 *            the name of the expression being edited.
	 */
	private void convert(Expr ccall, List<CommandInterface> commandList,
			boolean isSuper)
	{
		if (commandList != null)
		{
			for (CommandInterface curCommand : commandList)
			{

				try
				{
					curCommand.translate(ccall, isSuper);
				}
				catch (Exception e)
				{
					e.printStackTrace();
					System.err
							.println("Command is being removed because it is incorrectly configured");
					commandList.remove(curCommand);

				}
			}
		}
	}

	public List<CommandInterface> getCommandList(SignatureType sigType,
			String signature)
	{
		Map<String, List<CommandInterface>> callToCommand = ExpressionNameToCommand
				.get(sigType);
		if (callToCommand != null)
		{
			return callToCommand.get(signature);
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.Handler)
	 */
	public void edit(Handler ccall)
	{
		try
		{
			String handlerSig = ccall.getType().getName();
			convert(ccall, getCommandList(SignatureType.HANDLER, handlerSig),
					false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Could not find class for handled class type");
			e.printStackTrace();
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.Cast)
	 */
	public void edit(Cast ccall)
	{

		try
		{
			String castSig = ccall.getType().getName();
			convert(ccall, getCommandList(SignatureType.CAST, castSig), false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Could not find class for cast type");
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.Instanceof)
	 */
	public void edit(Instanceof ccall)
	{
		try
		{
			String instanceofSig = ccall.getType().getName();
			convert(ccall, getCommandList(SignatureType.INSTANCEOF,
					instanceofSig), false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Could not find class for array type");
			e.printStackTrace();
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.FieldAccess)
	 */
	public void edit(FieldAccess ccall)
	{
		// TODO: This probably does not work! If you attempt to access the field
		// in a super class this may fall apart.
		try
		{
			String fieldName = ccall.getField().getName();
			convert(ccall,
					getCommandList(SignatureType.FIELDACCESS, fieldName), false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Could not find class for array type");
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.NewArray)
	 */
	public void edit(NewArray ccall)
	{

		try
		{
			String arrayTypeSig = ccall.getComponentType().getName();
			convert(ccall,
					getCommandList(SignatureType.NEWARRAY, arrayTypeSig), false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Could not find class for array type");
			e.printStackTrace();
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.MethodCall)
	 */
	public void edit(MethodCall ccall)
	{
		try
		{
			String constName = ccall.getMethod().getLongName();
			convert(ccall, getCommandList(SignatureType.METHODCALL, constName),
					ccall.isSuper());
		}
		catch (NotFoundException e)
		{
			System.err.println("Cannot find Method");
			e.printStackTrace();

		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.ConstructorCall)
	 */
	public void edit(ConstructorCall ccall)
	{
		try
		{
			String constName = ccall.getConstructor().getLongName();
			convert(ccall, getCommandList(SignatureType.CONSTRUCTORCALL,
					constName), ccall.isSuper());
		}
		catch (NotFoundException e)
		{
			System.err.println("Cannot find Constructor");
			e.printStackTrace();

		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javassist.expr.ExprEditor#edit(javassist.expr.NewExpr)
	 */
	public void edit(NewExpr ccall)
	{
		try
		{
			CtConstructor tempConst = ccall.getConstructor();
			String constName = tempConst.getLongName();
			convert(ccall, getCommandList(SignatureType.NEWEXPR, constName), false);
		}
		catch (NotFoundException e)
		{
			System.err.println("Cannot find Constructor");
			e.printStackTrace();

		}

	}

}
